package core;
import stat.*;

import com.brsanthu.dataexporter.*;
import com.brsanthu.dataexporter.output.csv.*;
import com.brsanthu.dataexporter.output.html.*;



import com.brsanthu.dataexporter.output.text.*;
import com.brsanthu.dataexporter.output.xml.*;

import static com.googlecode.javacv.cpp.opencv_core.CV_FILLED;
import static com.googlecode.javacv.cpp.opencv_core.CV_RGB;
import static com.googlecode.javacv.cpp.opencv_core.IplImage;
import static com.googlecode.javacv.cpp.opencv_core.cvDrawContours;
import static com.googlecode.javacv.cpp.opencv_core.cvPoint;
import com.googlecode.javacv.CanvasFrame;
import static com.googlecode.javacv.cpp.opencv_highgui.cvLoadImage;
import static com.googlecode.javacv.cpp.opencv_highgui.cvSaveImage;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Vector;
import java.util.regex.Pattern;


import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.DefaultTableCellRenderer;

public class BVModel {
	private IplImage original;
	private IplImage result;
	private BallDetector bDetector;
	private VoidDetector vDetector;
	private JTable dataTable;
	private Vector<Vector<Object>> tableData;
	private String path;
	private ArrayList<Stat> stat;


	private static final String FILE_DESCRIPTION_ALL_IMAGE = "Image File (jpg, png, bmp, ...)";
	private static final String FILE_DESCRIPTION_JPEG = "JPEG File (jpg, jpeg, ...)";
	private static final String FILE_DESCRIPTION_PNG = "PNG File (png, ...)";
	private static final String FILE_DESCRIPTION_BMP = "BITMAP File (bmp, ...)";
	private static final String FILE_DESCRIPTION_CSV = "CSV File (csv, ...)";
	private static final String FILE_DESCRIPTION_XML = "XML File (xml, ...)";
	private static final String FILE_DESCRIPTION_TEXT = "Text File (txt, ...)";
	private static final String FILE_DESCRIPTION_HTML = "HTML File (htm, html, ...)";
	
	private static final Pattern EXTENSION_PATTERN_JPEG = Pattern.compile("(.*)(jpg|jpeg)$", Pattern.CASE_INSENSITIVE);
	private static final Pattern EXTENSION_PATTERN_PNG = Pattern.compile("(.*)(png)$", Pattern.CASE_INSENSITIVE);
	private static final Pattern EXTENSION_PATTERN_BMP = Pattern.compile("(.*)(bmp)$", Pattern.CASE_INSENSITIVE);
	private static final Pattern EXTENSION_PATTERN_CSV = Pattern.compile("(.*)(csv)$", Pattern.CASE_INSENSITIVE);
	private static final Pattern EXTENSION_PATTERN_XML = Pattern.compile("(.*)(xml)$", Pattern.CASE_INSENSITIVE);
	private static final Pattern EXTENSION_PATTERN_TEXT = Pattern.compile("(.*)(txt)$", Pattern.CASE_INSENSITIVE);
	private static final Pattern EXTENSION_PATTERN_HTML = Pattern.compile("(.*)(html|html)$", Pattern.CASE_INSENSITIVE);
	
	
	private static final FileFilter filterAllImage = new FileNameExtensionFilter(FILE_DESCRIPTION_ALL_IMAGE, "jpg", "jpeg", "png", "bmp");
	private static final FileFilter filterJpeg = new FileNameExtensionFilter(FILE_DESCRIPTION_JPEG, "jpg", "jpeg");
	private static final FileFilter filterPng = new FileNameExtensionFilter(FILE_DESCRIPTION_PNG, "png");
	private static final FileFilter filterBitmap = new FileNameExtensionFilter(FILE_DESCRIPTION_BMP, "bmp");

	private static final FileFilter filterCSV = new FileNameExtensionFilter(FILE_DESCRIPTION_CSV, "csv");
	//private static final FileFilter filterXML = new FileNameExtensionFilter(FILE_DESCRIPTION_XML, "xml");
	private static final FileFilter filterTEXT = new FileNameExtensionFilter(FILE_DESCRIPTION_TEXT, "txt");
	//private static final FileFilter filterHTML= new FileNameExtensionFilter(FILE_DESCRIPTION_HTML, "html", "htm");

	public BVModel() {
		this.original = null;
		this.result = null;
		this.bDetector = null;
		this.vDetector = null;
		this.stat = null;
	}

	public IplImage getOriginalImage() {
		return this.original;
	}

	public IplImage getResultImage() {
		return this.result;
	}

	public JTable getDataTable() {
		return dataTable;
	}

	public String getPath() {
		return path;
	}
	
	// For debug. Print the original image.
	public void printOriginalImage() {
		CanvasFrame original = new CanvasFrame("original");
		original.showImage(this.getOriginalImage());
	}

	// For debug. Print the result image.
	public void printResultImage(String done) {
		CanvasFrame processed = new CanvasFrame(done);
		processed.showImage(this.getResultImage());
	}

	public void loadImage(JFrame mainFrame) {
		// Set the base directory as [BVProject]/images
		// If you want add images, then add from package explore and commit
		// them.
		String basePath = System.getProperty("user.dir");
		JFileChooser fileChooser = new JFileChooser(basePath);
		fileChooser.setAcceptAllFileFilterUsed(false);
		fileChooser.addChoosableFileFilter(filterPng);
		fileChooser.addChoosableFileFilter(filterJpeg);
		fileChooser.addChoosableFileFilter(filterBitmap);
		fileChooser.addChoosableFileFilter(filterAllImage);
		
		// TODO catch null pointer exception
		int userSelection = fileChooser.showOpenDialog(mainFrame);
		
		// if user's select is load
		if (userSelection == JFileChooser.APPROVE_OPTION) {
			// Load the image
			this.path = fileChooser.getSelectedFile().getAbsolutePath();
			final IplImage newImage = cvLoadImage(path);
			
			this.original = newImage;
			this.initBallDetector();
		}
	}
	
	public void initBallDetector() {
		this.bDetector = new BallDetector(this.original);
	}


	public void printHist() {
		bDetector.printHist();
	}

	public void ballPreprocess() {
		result = bDetector.thresholding();
		result = bDetector.cannying();
	}
	
	public void houghCircles() {
		result = bDetector.houghCircles(); 
	}
	
	public void houghCirclesFilter() {
		result = bDetector.houghCirclesFilter();
	}

	public void findContours() {
		result = bDetector.findContours();
	}

	public void filterContoursByCompactness() {
		result = bDetector.filterContoursByCompactness();
	}
	public void filterContoursByPixel() {
		bDetector.filterContoursByPixel();
	}	
	public void mergeCirclesAndContours() {
		result = bDetector.mergeCirclesAndContours();
	}
	public void filterContoursByDiameter() {
		bDetector.filterContoursByDiameter();
	}
	public void sortBallsByOrder() {
		bDetector.sortBallsByOrder();
	}

	public void postProcess() {
		bDetector.postProcess();
	}
	public IplImage writeNumbers(IplImage original) {
		return bDetector.writeNumbers(original);
		//result = bDetector.writeNumbers();
	}

	public void drawResult() {
		result = original.clone();
		for(int i = 0; i < bDetector.getBallList().size(); i++)
			cvDrawContours(result, bDetector.getBallList().get(i).getBallContour(), CV_RGB(0,0,255), CV_RGB(0,0,0), 0, 2, 8, cvPoint(0,0));
		for(int i = 0; i < vDetector.getVoidsAL().size(); i++)
			cvDrawContours(result, vDetector.getVoidsAL().get(i), CV_RGB(0,255,0), CV_RGB(0,255,0), -1, CV_FILLED, 8, cvPoint(0,0));
		result = this.writeNumbers(result);
	}
	public void detectBalls() {
		this.initBallDetector();
		this.ballPreprocess();
		//this.printResultImage("pre");
		this.houghCircles();
		//this.printResultImage("hough");

		this.findContours();
		this.filterContoursByCompactness();
		//this.printResultImage("contour");

		this.houghCirclesFilter();
		//this.printResultImage("hough filter");

		this.mergeCirclesAndContours();
		//this.printResultImage("merge");
		//this.filterContoursByPixel();
		this.filterContoursByDiameter();
		this.sortBallsByOrder();
		this.postProcess();
	}
	
	// Initialize a void detector.
	public void initVoidDetector() {
		vDetector = new VoidDetector(this.original, bDetector.getBallsAL());
	}
	
	// Preprocess for void detection
	public void voidPreprocess() {
		vDetector.preprocessing();
	}
	
	// Call the detecting function in the void detector
	public void divideAndConquer(boolean debug) {
		result = vDetector.divideAndConquer(debug);
	}
	
	// Do void detection
	public void detectVoids() {
		this.initVoidDetector();
		this.voidPreprocess();
		// If you need a debug for void detection, then change the boolean into true
		this.divideAndConquer(false);
	}

	public void saveImage(JFrame mainFrame) {		
		// If the result is not set yet, then raise NullPointerException
		if (this.result.isNull()) throw new NullPointerException();
		
		JFileChooser fileChooser = new JFileChooser(System.getProperty("user.home"));

		fileChooser.setAcceptAllFileFilterUsed(false);
		fileChooser.addChoosableFileFilter(filterPng);
		fileChooser.addChoosableFileFilter(filterJpeg);
		fileChooser.addChoosableFileFilter(filterBitmap);
		
		
		fileChooser.setDialogTitle("Specify a file to save");
		int userSelection = fileChooser.showSaveDialog(mainFrame);
		 
		if (userSelection == JFileChooser.APPROVE_OPTION) {
		    File fileToSave = fileChooser.getSelectedFile();
		    String extension = fileChooser.getFileFilter().getDescription();
		    String filePath = fileToSave.getAbsolutePath();
		    
		    if(extension.equals(FILE_DESCRIPTION_JPEG) && !EXTENSION_PATTERN_JPEG.matcher(filePath).matches()) {
		    	filePath = filePath.concat(".jpg");
			    cvSaveImage(filePath, this.getResultImage());
		    } else if (extension.equals(FILE_DESCRIPTION_PNG) && !EXTENSION_PATTERN_PNG.matcher(filePath).matches()) {
		    	filePath = filePath.concat(".png");
			    cvSaveImage(filePath, this.getResultImage());
		    } else if (extension.equals(FILE_DESCRIPTION_BMP) && !EXTENSION_PATTERN_BMP.matcher(filePath).matches()) {
		    	filePath = filePath.concat(".bmp");
			    cvSaveImage(filePath, this.getResultImage());
		    } else {
		    	System.exit(-1);
		    }
		}
		
		
	}

	public void generateStat() {
		this.stat = new ArrayList<Stat>();
		ArrayList<BallStat> ballStat = bDetector.getBallStat();
		ArrayList<VoidStat> voidStat = vDetector.getVoidStat();	
		
		if (ballStat.size() != voidStat.size()) throw new IllegalStateException();
		
		for ( int i=0; i < ballStat.size(); i++ ){
			stat.add(new Stat(i, ballStat.get(i), voidStat.get(i)));
		}
	}
	
	public void generateData() {
		this.generateStat();
		// The column names used for table headings
		Vector<String> columnNames = new Vector<String>();
		columnNames.addElement("No.");
		columnNames.addElement("Void");
		columnNames.addElement("V Area");
		columnNames.addElement("B Area");
		columnNames.addElement("Ratio");		
		
		// Set the rowData
		Vector<Vector<Object>> rowData = new Vector<Vector<Object>>();
		for( int i=0; i < stat.size(); i++ ) {
			Vector<Object> row = new Vector<Object>();
			row.addElement(new Integer(i+1));
			row.addElement(stat.get(i).getvNums());
			row.addElement(stat.get(i).getvArea());
			row.addElement(stat.get(i).getbArea());
			row.addElement(stat.get(i).getRatio());
			rowData.addElement(row);
		}

		this.tableData = rowData;
		this.dataTable = new JTable(new ResultTableModel(columnNames, rowData));

		DefaultTableCellRenderer rightRenderer = new DefaultTableCellRenderer();
		rightRenderer.setHorizontalAlignment( JLabel.RIGHT );
		
		dataTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		dataTable.getColumnModel().getColumn(0).setPreferredWidth(25);
		dataTable.getColumnModel().getColumn(1).setPreferredWidth(30);
		dataTable.getColumnModel().getColumn(2).setPreferredWidth(50);
		dataTable.getColumnModel().getColumn(3).setPreferredWidth(50);
		dataTable.getColumnModel().getColumn(0).setCellRenderer(rightRenderer);
		dataTable.getColumnModel().getColumn(1).setCellRenderer(rightRenderer);
		dataTable.getColumnModel().getColumn(2).setCellRenderer(rightRenderer);
		dataTable.getColumnModel().getColumn(3).setCellRenderer(rightRenderer);
		dataTable.getColumnModel().getColumn(4).setCellRenderer(rightRenderer);
		dataTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
		
	}

	public void exportData(JFrame mainFrame) throws FileNotFoundException {
		JFileChooser fileChooser = new JFileChooser(System.getProperty("user.home"));

		fileChooser.setAcceptAllFileFilterUsed(false);
		fileChooser.addChoosableFileFilter(filterCSV);
		//fileChooser.addChoosableFileFilter(filterXML);
		fileChooser.addChoosableFileFilter(filterTEXT);
		//fileChooser.addChoosableFileFilter(filterHTML);
		
		fileChooser.setDialogTitle("Specify a file to save");
		int userSelection = fileChooser.showSaveDialog(mainFrame);
		 
		if (userSelection == JFileChooser.APPROVE_OPTION) {
		    File fileToSave = fileChooser.getSelectedFile();
		    String extension = fileChooser.getFileFilter().getDescription();
		    String filePath = fileToSave.getAbsolutePath();
		    
		    DataExporter exporter;
		    if(extension.equals(FILE_DESCRIPTION_CSV) && !EXTENSION_PATTERN_CSV.matcher(filePath).matches()) {
		    	filePath = filePath.concat(".csv");
			    FileOutputStream stream = new FileOutputStream(filePath);
		    	exporter = new CsvExporter(stream);
			    this.writeExporter(exporter);
		    } else if (extension.equals(FILE_DESCRIPTION_XML) && !EXTENSION_PATTERN_XML.matcher(filePath).matches()) {
		    	filePath = filePath.concat(".xml");
			    FileOutputStream stream = new FileOutputStream(filePath);
		    	exporter = new XmlExporter(stream);
			    this.writeExporter(exporter);
		    } else if (extension.equals(FILE_DESCRIPTION_HTML) && !EXTENSION_PATTERN_HTML.matcher(filePath).matches()) {
		    	filePath = filePath.concat(".html");
			    FileOutputStream stream = new FileOutputStream(filePath);
		    	exporter = new HtmlExporter(stream);
		    } else if (extension.equals(FILE_DESCRIPTION_TEXT) && !EXTENSION_PATTERN_TEXT.matcher(filePath).matches()) {
		    	filePath = filePath.concat(".txt");
			    FileOutputStream stream = new FileOutputStream(filePath);
		    	exporter = new TextExporter(stream);
			    this.writeExporter(exporter);
		    } else {
		    	System.exit(-1);
		    }
		}
	}

	public void writeExporter(DataExporter exporter) {
		// First, add columns to use
		
		exporter.addColumn("No");
		exporter.addColumn("Void Numbers");
		exporter.addColumn("Void Area");
		exporter.addColumn("Ball Area");
		exporter.addColumn("Ratio");
		
		// Add all the data into exporter
		for( int i=0 ; i < tableData.size(); i++ ) {
			Vector<Object> row = tableData.get(i);
			exporter.addRow(row.get(0), row.get(1), row.get(2), row.get(3), row.get(4));
		}
		
		// Finish the exporter
		exporter.finishExporting();
	}
	
}
